package github.sf.fw.tools.db.ddldiff;

import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import github.sf.fw.utils.StrUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.util.ArrayList;
import java.util.List;

public class RdbmsDDLUpdater extends DDLUpdater {


    /**
     * 根据老的建表语句和新的建表语句创建表更新语句,
     * 暂只支持老表和新表的列顺序要相同，且新表不能在表的中间修改字段值，否则会直接使用新表的建表语句
     *
     * @param oldDDL 表格原来的建表语句
     * @param newDDL 表格新的建表语句
     * @return 表格更新语句
     */
    @Override
    public String createUpdateTableDDL(String oldDDL, String newDDL) {
        Pair<String, List<SQLColumnDefinition>> oldTaleAndColumn = getColumns(oldDDL);
        List<SQLColumnDefinition> oldColumns = oldTaleAndColumn.getRight();
        Pair<String, List<SQLColumnDefinition>> newTaleAndColumn = getColumns(newDDL);
        List<SQLColumnDefinition> newColumns = newTaleAndColumn.getRight();
        if (nonEqualIgnoreSpecialChar(oldTaleAndColumn.getLeft(), newTaleAndColumn.getLeft())) {
            return newDDL;
        }
        StringBuilder updateDDL = new StringBuilder();
        int i = 0;
        boolean useNewDDL = false;
        for (; i < oldColumns.size(); i++) {
            SQLColumnDefinition oldColum = oldColumns.get(i);
            if (i < newColumns.size()) {
                SQLColumnDefinition newColumn = newColumns.get(i);
                if (!columnEqual(oldColum, newColumn)) {
                    useNewDDL = true;
                    break;
                }
            } else {
                updateDDL.append("alter table ").append(newTaleAndColumn.getLeft()).append(" drop column ").append(oldColum.getColumnName()).append(";\n");
            }
        }
        if (useNewDDL) {
            return newDDL;
        }
        for (; i < newColumns.size(); i++) {
            SQLColumnDefinition newColumn = newColumns.get(i);
            updateDDL.append("alter table ").append(newTaleAndColumn.getLeft()).append(" add column ").append(newColumn.getColumnName()).append(" ").append(newColumn.getDataType().toString()).append(";\n");
        }
        return updateDDL.toString();
    }

    /**
     * 两个DDL的表名应该相同，若表名不同会报错
     *
     * @param oldDDL 老的DDL
     * @param newDDL 新的DDL
     * @return 生成的DDL更新语句
     */
    @Override
    public String smartCreateUpdateTableDDL(String oldDDL, String newDDL) {
        Pair<String, List<SQLColumnDefinition>> oldTaleAndColumn = getColumns(oldDDL);
        List<SQLColumnDefinition> oldColumns = oldTaleAndColumn.getRight();
        Pair<String, List<SQLColumnDefinition>> newTaleAndColumn = getColumns(newDDL);
        List<SQLColumnDefinition> newColumns = newTaleAndColumn.getRight();
        if (!DDLUpdater.pureColumnName(oldTaleAndColumn.getLeft()).equals(DDLUpdater.pureColumnName(newTaleAndColumn.getLeft()))) {
            throw new UnsupportedOperationException(StrUtil.format("{} 和 {} 表名不同，暂不支持做差异对比", oldTaleAndColumn.getLeft(), newTaleAndColumn.getLeft()));
        }
        SQLColumnDefinitionAnalysisContext analysisContext = analyzeColumnDefinition(oldColumns, newColumns);
        List<String> tableUpdateDDL = createTableUpdateDDL(oldTaleAndColumn.getLeft(), analysisContext.getSqlColumnDefinitionUpdateStatuses());
        return String.join("\n", tableUpdateDDL);
    }

    @Override
    protected SQLColumnDefinitionAnalysisContext analyzeColumnDefinition(List<SQLColumnDefinition> oldColumnDefinitions, List<SQLColumnDefinition> newColumnDefinitions) {
        SQLColumnDefinitionAnalysisContext analysisContext = new SQLColumnDefinitionAnalysisContext();
        if (CollectionUtils.isEmpty(newColumnDefinitions)) {
            analysisContext.setUseNewDDL(true);
            return analysisContext;
        }
        ColumnDefinitionDiff columnDefinitionDiff = new ColumnDefinitionDiff(oldColumnDefinitions, newColumnDefinitions);
        return columnDefinitionDiff.diff();
    }

    @Override
    protected List<String> createTableUpdateDDL(String tableName, List<SQLColumnDefinitionUpdate> sqlColumnDefinitionUpdateStatuses) {
        List<String> updateDLLs = new ArrayList<>();
        for (SQLColumnDefinitionUpdate sqlColumnDefinitionUpdateStatus : sqlColumnDefinitionUpdateStatuses) {
            ColumnDefinitionUpdateStatus updateStatus = sqlColumnDefinitionUpdateStatus.getColumnDefinitionUpdateStatus();
            SQLColumnDefinition columnDefinition = sqlColumnDefinitionUpdateStatus.getSqlColumnDefinition();
            switch (updateStatus) {
                case ADD:
                    if (sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition() != null) {
                        updateDLLs.add(StrUtil.format("alter table {} add column {} {} {} {};", tableName,
                                columnDefinition.getColumnName(),
                                columnDefinition.getDataType(),
                                sqlColumnDefinitionUpdateStatus.getInsertType().name(),
                                sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition().getColumnName()
                            )
                        );
                    } else {
                        updateDLLs.add(StrUtil.format("alter table {} add column {} {};", tableName,
                            columnDefinition.getColumnName(),
                            columnDefinition.getDataType()));
                    }
                    break;
                case MODIFY:
                    updateDLLs.add(StrUtil.format("alter table {} modify column {} {};", tableName,
                        columnDefinition.getColumnName(),
                        columnDefinition.getDataType()));
                    break;
                case DROP:
                    updateDLLs.add(StrUtil.format("alter table {} drop column {} {};",
                        tableName,
                        columnDefinition.getColumnName(),
                        columnDefinition.getDataType()));
                    break;
                case MOVE:
                    if (sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition() != null) {
                        if ("first".equalsIgnoreCase(sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition().getColumnName())) {
                            updateDLLs.add(StrUtil.format("alter table {} modify column {} {};",
                                tableName,
                                columnDefinition.getColumnName(),
                                sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition().getColumnName()
                            ));
                        } else {
                            updateDLLs.add(StrUtil.format("alter table {} modify column {} {} {};",
                                tableName,
                                columnDefinition.getColumnName(),
                                sqlColumnDefinitionUpdateStatus.getInsertType().name(),
                                sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition().getColumnName()
                            ));
                        }
                    }
                    break;
                case MODIFY_AND_MOVE:
                    if (sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition() != null) {
                        if ("first".equalsIgnoreCase(sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition().getColumnName())) {
                            updateDLLs.add(StrUtil.format("alter table {} modify column {} {} {};",
                                tableName,
                                columnDefinition.getColumnName(),
                                columnDefinition.getDataType(),
                                sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition().getColumnName()
                            ));
                        } else {
                            updateDLLs.add(StrUtil.format("alter table {} modify column {} {} {} {};",
                                tableName,
                                columnDefinition.getColumnName(),
                                columnDefinition.getDataType(),
                                sqlColumnDefinitionUpdateStatus.getInsertType().name(),
                                sqlColumnDefinitionUpdateStatus.getLocationColumnDefinition().getColumnName()
                            ));
                        }
                    } else {
                        updateDLLs.add(StrUtil.format("alter table {} modify column {} {};",
                            tableName,
                            columnDefinition.getColumnName(),
                            columnDefinition.getDataType()
                        ));
                    }
                    break;
            }
        }
        return updateDLLs;
    }

    public String removeDorisSpecialStatement(String dorisCreateTableDDL) {
        if (StrUtil.isEmpty(dorisCreateTableDDL)) {
            throw new IllegalArgumentException("dorisCreateTableDDL不能为空");
        }
        String pattern = "(?!\\))\\s+(ENGINE|UNIQUE KEY).+[\\);]$";
        String ddl0 = dorisCreateTableDDL.replaceAll("\\n", " ");
        return ddl0.replaceAll(pattern, "");
    }
}
